17. Dependency Injection

Dependency Injection

ND079 JPND C2 L03 A18 Dependency Injection V3

What is a Dependency?

A dependency is anything your code needs to work, such as an external library, an environment variable, a remote website, or a database.

In the context of dependency injection, a dependency usually refers to an object, class, or interface that your code imports, creates, or uses.

What is Dependency Injection?

Dependency Injection, or DI, is a design pattern that moves the creation of dependencies to outside of your code. Instead of creating objects, you tell the DI framework to create the objects for you, and then you inject those objects into your class.

Which of the following are examples of program dependencies?

SOLUTION:
  • The program imports a library.
  • The program requires a database to be present at a specific network address.
  • Java 8+ needs to be installed for the program to use default methods in interfaces.
  • The `PATH` environment variable needs to contain a specific binary that the program invokes.
  • The program can only successfully run on a PC with 2GB of memory or more.
  • The program imports a library that, in turn, requires a JAR containing version 9 of the Apache Commons library to be in the Java classpath.

What's the main problem Dependency Injection solves?

SOLUTION: Object creation. Instead of creating objects yourself, the DI framework does it for you.

Using @Inject Annotations

To inject objects from a DI framework, you can add @Inject annotations to your code. You can add them directly to instance fields:

class CourseRegistrar {
  @Inject private Database db;
  @Inject private Clock clock;
  @Inject private RegistrationFactory factory;

  boolean registerStudentForCourse(Student s, int courseId) {
    Course c = db.getCourse(courseId);
    if (clock.instant().isAfter(c.getRegistrationDeadline())) return false;
    if (!s.getPassedCourses().containsAll(c.getPrereqs())) return false;
    db.createRegistration(factory.create(courseId, s.getId()));
    return true;
  }
}

… or, you can add @Inject annotations to constructors:

class CourseRegistrar {
  private final Database db;
  private final Clock clock;
  private final RegistrationFactory factory;

  @Inject
  CourseRegistrar(Database db, Clock clock, RegistrationFactory factory) {
    this.db = db;
    this.clock = clock;
    this.factory = factory;
  }

  boolean registerStudentForCourse(Student s, int courseId) {
    Course c = db.getCourse(courseId);
    if (clock.instant().isAfter(c.getRegistrationDeadline())) return false;
    if (!s.getPassedCourses().containsAll(c.getPrereqs())) return false;
    db.createRegistration(factory.create(courseId, s.getId()));
    return true;
  }
}

It's up to you do decide where to place @Inject annotations in your code. Annotating fields directly can save some lines of code, while annotating constructors allows you to mark the fields final.

What is the annotation Java annotation for injection code dependencies?

SOLUTION: `@Inject`

How DI Injects Objects

The DI framework will attempt to instantiate any object that's injected. The DI framework will fail at runtime if it doesn't know how to create the injected object. DI frameworks use modules to configure which classes or objects should be used when an interface is injected.

Indirect Dependencies

DI also takes care of indirect, or transitive, dependencies. If you @Inject a class, and that class also has a constructor marked with @Inject, the DI framework will try to inject all the constructor dependencies, and all those dependencies' dependencies, and so on.

Using DI to Create Singletons

DI frameworks can usually be configured to return a specific instance of an object whenever it's injected. Any time that object is requested by an @Inject annotation, the DI framework will supply the exact same instance, making it effectively a singleton.

How does dependency injection help with testing software?

SOLUTION: It makes easier to simulate behavior by injecting fake objects.

Further Reading